#include "HidDevice.h"

#include <iostream>

bool TLAC::Input::RawInputDetails::HidDevice::OpenHandle(HANDLE handle)
{
	UINT size{ 0 };
	GetRawInputDeviceInfo(handle, RIDI_PREPARSEDDATA, NULL, &size);
	preparsed_data_.reset((PHIDP_PREPARSED_DATA)std::malloc(size));
	if (GetRawInputDeviceInfo(handle, RIDI_PREPARSEDDATA, preparsed_data_.get(), &size) == (UINT)-1) {
		std::wcout << L"[TLAC] HidDevice::OpenHandle(): GetRawInputDeviceInfo() getting preparsed data Error: " << GetLastError() << std::endl;
		return false;
	}

	NTSTATUS status;
	capabilities_ = std::make_unique<HIDP_CAPS>();
	if ((status = HidP_GetCaps(preparsed_data_.get(), capabilities_.get())) != HIDP_STATUS_SUCCESS) {
		std::wcout << L"[TLAC] HidDevice::OpenHandle(): HidP_GetCaps() failed wit NTSTATUS: " << status << std::endl;
		return false;
	}

	input_data_buffer_maxlen_ = HidP_MaxDataListLength(HidP_Input, preparsed_data_.get());
	input_data_buffer_.reserve(input_data_buffer_maxlen_);

	/* Note: the above code is all that's required to parse input reports.
	Consider splitting code below to a separate class if you don't need to send output reports. */

	size = 0;
	GetRawInputDeviceInfo(handle, RIDI_DEVICENAME, NULL, &size);
	std::vector<TCHAR> device_name(size);
	if (GetRawInputDeviceInfo(handle, RIDI_DEVICENAME, device_name.data(), &size) == (UINT)-1) {
		std::wcout << L"[TLAC] HidDevice::OpenHandle(): GetRawInputDeviceInfo() getting device name Error: " << GetLastError() << std::endl;
		return false;
	}
	file_.reset(CreateFile(
		device_name.data(),
		GENERIC_READ | GENERIC_WRITE,
		FILE_SHARE_READ | FILE_SHARE_WRITE,
		NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL));
	if (!file_) {
		std::wcout << L"[TLAC] HidDevice::OpenHandle(): CreateFile() Error: " << GetLastError() << std::endl;
		return false;
	}
	std::vector<TCHAR> manufacturer_string(127), product_string(127);
	if (!HidD_GetManufacturerString(file_.get(), manufacturer_string.data(), sizeof(wchar_t) * manufacturer_string.size())) {
		std::wcout << L"[TLAC] HidDevice::OpenHandle(): HidD_GetManufacturerString() Error: " << GetLastError() << std::endl;
	}
	if (!HidD_GetProductString(file_.get(), product_string.data(), sizeof(wchar_t) * product_string.size())) {
		std::wcout << L"[TLAC] HidDevice::OpenHandle(): HidD_GetManufacturerString() Error: " << GetLastError() << std::endl;
	}
	std::wcout << L"[TLAC] Opened HID device " << manufacturer_string.data() << L" : " << product_string.data() << std::endl;

	return true;
}

std::vector<HIDP_BUTTON_CAPS> TLAC::Input::RawInputDetails::HidDevice::GetSpecificButtonCaps(
	HIDP_REPORT_TYPE ReportType, USAGE UsagePage, USHORT LinkCollection, USAGE Usage)
{
	USHORT num_button_caps{ 0 };
	switch (ReportType) {
	case HidP_Input:
		num_button_caps = capabilities_->NumberInputButtonCaps;
		break;
	case HidP_Output:
		num_button_caps = capabilities_->NumberOutputButtonCaps;
		break;
	case HidP_Feature:
		num_button_caps = capabilities_->NumberFeatureButtonCaps;
		break;
	}
	std::vector<HIDP_BUTTON_CAPS> button_caps(num_button_caps);
	if (num_button_caps) {
		NTSTATUS status;
		if ((status = HidP_GetSpecificButtonCaps(
			ReportType, UsagePage, LinkCollection, Usage, button_caps.data(), &num_button_caps, preparsed_data_.get()
		)) != HIDP_STATUS_SUCCESS)
		{
			if (status == HIDP_STATUS_USAGE_NOT_FOUND) {
				// No matching usages found. Just let clear() below do its job.
			}
			else {
				std::wcout << L"[TLAC] HidP_GetSpecificButtonCaps() failed with NTSTATUS: " << status << std::endl;
			}
			button_caps.clear();
		}
		else {
			button_caps.resize(num_button_caps);
		}
	}
	return button_caps;
}

std::vector<HIDP_VALUE_CAPS> TLAC::Input::RawInputDetails::HidDevice::GetSpecificValueCaps(
	HIDP_REPORT_TYPE ReportType, USAGE UsagePage, USHORT LinkCollection, USAGE Usage)
{
	USHORT num_value_caps{ 0 };
	switch (ReportType) {
	case HidP_Input:
		num_value_caps = capabilities_->NumberInputValueCaps;
		break;
	case HidP_Output:
		num_value_caps = capabilities_->NumberOutputValueCaps;
		break;
	case HidP_Feature:
		num_value_caps = capabilities_->NumberFeatureValueCaps;
		break;
	}
	std::vector<HIDP_VALUE_CAPS> value_caps(num_value_caps);
	if (num_value_caps) {
		NTSTATUS status;
		if ((status = HidP_GetSpecificValueCaps(
			ReportType, UsagePage, LinkCollection, Usage, value_caps.data(), &num_value_caps, preparsed_data_.get()
		)) != HIDP_STATUS_SUCCESS)
		{
			if (status == HIDP_STATUS_USAGE_NOT_FOUND) {
				// No matching usages found. Just let clear() below do its job.
			}
			else {
				std::wcout << L"[TLAC] HidP_GetSpecificValueCaps() failed wit NTSTATUS: " << status << std::endl;
			}
			value_caps.clear();
		}
		else {
			value_caps.resize(num_value_caps);
		}
	}
	return value_caps;
}

const std::vector<HIDP_DATA>& TLAC::Input::RawInputDetails::HidDevice::GetDataFromInputReport(
	PCHAR Report, ULONG ReportLength)
{
	input_data_buffer_.clear();
	input_data_buffer_.resize(input_data_buffer_maxlen_);
	ULONG length = input_data_buffer_.size();
	NTSTATUS status;
	if ((status = HidP_GetData(
		HidP_Input, input_data_buffer_.data(), &length, preparsed_data_.get(), Report, ReportLength
	)) != HIDP_STATUS_SUCCESS)
	{
		std::wcout << L"[TLAC] HidP_GetData() failed with NTSTATUS: " << status << std::endl;
		input_data_buffer_.clear();
	}
	else {
		input_data_buffer_.resize(length);
	}
	return input_data_buffer_;
}

bool TLAC::Input::RawInputDetails::HidDevice::InitializeReportForID(
	HIDP_REPORT_TYPE ReportType, UCHAR ReportID, std::vector<CHAR>& Report)
{
	USHORT report_length{ 0 };
	switch (ReportType) {
	case HidP_Input:
		report_length = capabilities_->InputReportByteLength;
		break;
	case HidP_Output:
		report_length = capabilities_->OutputReportByteLength;
		break;
	case HidP_Feature:
		report_length = capabilities_->FeatureReportByteLength;
		break;
	}
	Report.clear();
	Report.resize(report_length);
	NTSTATUS status;
	if ((status = HidP_InitializeReportForID(
		ReportType, ReportID, preparsed_data_.get(), Report.data(), Report.size()
	)) != HIDP_STATUS_SUCCESS)
	{
		std::wcout << L"[TLAC] HidP_InitializeReportForID() failed with NTSTATUS: " << status << std::endl;
		return false;
	}
	return true;
}

bool TLAC::Input::RawInputDetails::HidDevice::SetDataForOutputReport(std::vector<HIDP_DATA>& data_list, std::vector<CHAR>& report)
{
	ULONG data_length = data_list.size();
	NTSTATUS status;
	if ((status = HidP_SetData(
		HidP_Output, data_list.data(), &data_length, preparsed_data_.get(), report.data(), report.size()
	)) != HIDP_STATUS_SUCCESS)
	{
		std::wcout << L"[TLAC] HidP_SetData() failed with NTSTATUS: " << status << std::endl;
		return false;
	}
	return true;
}

bool TLAC::Input::RawInputDetails::AsyncWrite::Write(HANDLE file)
{
	if (WriteFile(file, buffer.data(), buffer.size(), NULL, &ov_)) {
		// Write has already completed
		write_pending_ = false;
	}
	else {
		auto error = GetLastError();
		if (error == ERROR_IO_PENDING) {
			// Write is in progress
			write_pending_ = true;
		}
		else {
			write_pending_ = false;
			std::wcout << L"[TLAC] WriteFile() Error: " << error << std::endl;
			return false;
		}
	}
	return true;
}

bool TLAC::Input::RawInputDetails::AsyncWrite::IsPending()
{
	if (!write_pending_) {
		return false;
	}
	if (HasOverlappedIoCompleted(&ov_)) {
		write_pending_ = false;
		return false;
	}
	return true;
}
